package org.example.auth.config;


import org.example.auth.config.handle.CustomClientCredentialsTokenEndpointFilter;
import org.example.auth.config.handle.CustomOAuth2AuthenticationEntryPoint;
import org.example.auth.config.handle.CustomWebResponseExceptionTranslator;
import org.example.auth.config.oauth.granter.EmailTokenGranter;
import org.example.auth.config.oauth.granter.MobileTokenGranter;
import org.example.auth.config.oauth.granter.MyResourceOwnerPasswordTokenGranter;
import org.example.auth.config.oauth.granter.SocialTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * OAuth2认证中心相关配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;
    @Resource
    private DefaultTokenServices authorizationServerTokenServices;
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Resource
    private AuthorizationCodeServices authorizationCodeServices;

    /**
     * 配置OAuth2客户端
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

    /**
     * 授权码存储位置
     * @return {@link AuthorizationCodeServices}
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new InMemoryAuthorizationCodeServices();
    }

    /**
     * 配置令牌端点（Token Endpoint）的安全约束
     *主要对一些端点的权限进行配置
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        CustomOAuth2AuthenticationEntryPoint authenticationEntryPoint= new CustomOAuth2AuthenticationEntryPoint();
        authenticationEntryPoint.setExceptionTranslator(new CustomWebResponseExceptionTranslator());
        CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security);
        endpointFilter.afterPropertiesSet();
        endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
        security.addTokenEndpointAuthenticationFilter(endpointFilter);

        security
                .authenticationEntryPoint(authenticationEntryPoint)
                //tokenkey端点完全公开
                .tokenKeyAccess("permitAll()")
                //checkToken端点完全公开
                .checkTokenAccess("permitAll()")
                //允许表单认证
                .allowFormAuthenticationForClients()
        ;

    }

    /**
     * 配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)
     * 目前这里仅仅配置了四个，分别如下：
     *
     * 配置了授权码模式所需要的服务，AuthorizationCodeServices
     * 配置了密码模式所需要的AuthenticationManager
     * 配置了令牌管理服务，AuthorizationServerTokenServices
     * 配置/oauth/token申请令牌的uri只允许POST提交。
     *
     *
     * spring Security框架默认的访问端点有如下6个：
     *
     * /oauth/authorize：获取授权码的端点
     * /oauth/token：获取令牌端点。
     * /oauth/confirm_access：用户确认授权提交端点。
     * /oauth/error：授权服务错误信息端点。
     * /oauth/check_token：用于资源服务访问的令牌解析端点。
     * /oauth/token_key：提供公有密匙的端点，如果你使用JWT令牌的话。
     * 当然如果业务要求需要改变这些默认的端点的url，也是可以修改的，AuthorizationServerEndpointsConfigurer有一个方法:
     * 第一个参数：需要替换的默认端点url
     * 第二个参数：自定义的端点url
     * public AuthorizationServerEndpointsConfigurer pathMapping(String defaultPath, String customPath)
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServices)
                .tokenServices(authorizationServerTokenServices)
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
        ;


        List tokenGranters = getDefaultTokenGranters(endpoints);
        tokenGranters.add(new EmailTokenGranter(authenticationManager, endpoints.getTokenServices(),
                clientDetailsService, endpoints.getOAuth2RequestFactory()));
        tokenGranters.add(new MobileTokenGranter(authenticationManager, endpoints.getTokenServices(),
                clientDetailsService, endpoints.getOAuth2RequestFactory()));
        tokenGranters.add(new SocialTokenGranter(authenticationManager, endpoints.getTokenServices(),
                clientDetailsService, endpoints.getOAuth2RequestFactory()));
        tokenGranters.add(new MyResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(),
                clientDetailsService, endpoints.getOAuth2RequestFactory()));
        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
    }



    private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints){
        List<TokenGranter> tokenGranters = new ArrayList<>();
        tokenGranters.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(),
                endpoints.getAuthorizationCodeServices(),clientDetailsService, endpoints.getOAuth2RequestFactory()));
        tokenGranters.add(new ImplicitTokenGranter(endpoints.getTokenServices(),
                clientDetailsService, endpoints.getOAuth2RequestFactory()));
        tokenGranters.add(new ClientCredentialsTokenGranter(endpoints.getTokenServices(),
                clientDetailsService, endpoints.getOAuth2RequestFactory()));
        tokenGranters.add(new RefreshTokenGranter(endpoints.getTokenServices(),
                clientDetailsService, endpoints.getOAuth2RequestFactory()));
//        tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(),
//                clientDetailsService, endpoints.getOAuth2RequestFactory()));
        return tokenGranters;
    }
}
